using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Threading;
using System.Collections.Generic;

namespace CustomWorkflowActivities
{
    /// <summary>
    /// synchronization service used by the real synchronization scope
    /// </summary>
    public class SynchronizationService
    {
        /// <summary>
        /// queue name used by the synchronization service to talk to the real synchronization scope activity
        /// </summary>
        internal const string QUEUE_NAME = "SynchronizationServiceQueue";

        /// <summary>
        /// object that controls the access to a resource for a given name
        /// </summary>
        internal class SynchronizationObject
        {
            private Mutex _mutex;
            private Queue<KeyValuePair<Guid, IComparable>> _waitingWorkflows = new Queue<KeyValuePair<Guid, IComparable>>();
            private Guid _currentWorkflow = Guid.Empty;

            /// <summary>
            /// the Mutex that controls access to this object
            /// </summary>
            internal Mutex Mutex
            {
                get
                {
                    return _mutex;
                }
                set
                {
                    _mutex = value;
                }
            }

            /// <summary>
            /// that list of workflows that are waiting for this resource
            /// </summary>
            internal Queue<KeyValuePair<Guid, IComparable>> WaitingWorkflows
            {
                get
                {
                    return _waitingWorkflows;
                }
            }

            /// <summary>
            /// the workflow that is currently using this resource
            /// </summary>
            internal Guid CurrentWorkflow
            {
                get
                {
                    return _currentWorkflow;
                }
                set
                {
                    _currentWorkflow = value;
                }
            }

            internal SynchronizationObject(string name)
            {
                _mutex = new Mutex(false, name);
            }
        }

        private Dictionary<string, SynchronizationObject> _synchronizationObjects = new Dictionary<string, SynchronizationObject>();

        private WorkflowRuntime runtime;

        /// <summary>
        /// the constructor for the synchronization service which needs to be added to the runtime
        /// in order for the real synchronization scope activity to work
        /// </summary>
        /// <param name="runtime">the workflow runtime</param>
        public SynchronizationService(WorkflowRuntime runtime)
        {
            this.runtime = runtime;
            runtime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(runtime_WorkflowTerminated);
        }

        void runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
        {
            lock (this)
            {
                foreach (SynchronizationObject synchronizationObject in _synchronizationObjects.Values)
                {
                    if (e.WorkflowInstance.InstanceId.Equals(synchronizationObject.CurrentWorkflow))
                    {
                        synchronizationObject.CurrentWorkflow = Guid.Empty;
                        StartNextWaitingWorkflow(synchronizationObject);
                    }
                }
            }
        }

        private SynchronizationObject GetSynchronizationObject(string name)
        {
            lock (this)
            {
                SynchronizationObject synchronizationObject;
                if (!_synchronizationObjects.TryGetValue(name, out synchronizationObject))
                {
                    synchronizationObject = new SynchronizationObject(name);
                    _synchronizationObjects.Add(name, synchronizationObject);
                }
                return synchronizationObject;
            }
        }

        internal void AddWaitingWorkflow(Guid id, IComparable queueName, string sharingName)
        {
            SynchronizationObject synchronizationObject = GetSynchronizationObject(sharingName);

            using (WaitHandleSentry sentry = new WaitHandleSentry(synchronizationObject.Mutex))
            {
                synchronizationObject.WaitingWorkflows.Enqueue(new KeyValuePair<Guid, IComparable>(id, queueName));
                if (synchronizationObject.CurrentWorkflow == Guid.Empty)
                {
                    StartNextWaitingWorkflow(synchronizationObject);
                }
            }
        }

        private void StartNextWaitingWorkflow(SynchronizationObject synchronizationObject)
        {
            using (WaitHandleSentry sentry = new WaitHandleSentry(synchronizationObject.Mutex))
            {
                synchronizationObject.CurrentWorkflow = Guid.Empty;
                if (synchronizationObject.WaitingWorkflows.Count > 0)
                {
                    KeyValuePair<Guid, IComparable> pair = synchronizationObject.WaitingWorkflows.Dequeue();
                    Guid id = pair.Key;
                    IComparable queueName = pair.Value;
                    WorkflowInstance instance = runtime.GetWorkflow(id);
                    instance.EnqueueItem(queueName, "dummy", null, null);
                    synchronizationObject.CurrentWorkflow = id;
                }
            }
        }

        internal void StartNextWaitingWorkflowByName(string sharingName)
        {
            SynchronizationObject synchronizationObject = GetSynchronizationObject(sharingName);
            StartNextWaitingWorkflow(synchronizationObject);
        }
    }

    [ToolboxBitmap(typeof(RealSynchronizationScope), "Resources.ActivityIcon.ico")]
    //[Designer(typeof(RealSynchronizationScopeDesigner), typeof(IDesigner))]
    [ActivityValidator(typeof(RealSynchronizationScopeValidator))]
    public partial class RealSynchronizationScope :
        SequenceActivity,
        IEventActivity,
        IActivityEventListener<QueueEventArgs>
    {
        [Description("The unique name for controlling access to a resource")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public static DependencyProperty SharingNameProperty = DependencyProperty.Register(
            "SharingName", typeof(string), typeof(RealSynchronizationScope));

        public string SharingName
        {
            get
            {
                return Convert.ToString(base.GetValue(SharingNameProperty));
            }

            set
            {
                base.SetValue(SharingNameProperty, value);
            }

        }

        /// <summary>
        /// the default constructor
        /// </summary>
        public RealSynchronizationScope()
        {
            InitializeComponent();
        }

        /// <summary>
        /// initializes an activity instance and creates to queue it uses
        /// </summary>
        /// <param name="provider"></param>
        protected override void Initialize(IServiceProvider provider)
        {
            base.Initialize(provider);

            WorkflowQueuingService qService = (WorkflowQueuingService)provider.GetService(typeof(WorkflowQueuingService));
            if (qService != null)
            {
                if (!qService.Exists(QueueName))
                {
                    WorkflowQueue queue = qService.CreateWorkflowQueue(QueueName, false);
                }
            }
        }

        /// <summary>
        /// removes to queue before this instance of an activity is uninitialized
        /// </summary>
        /// <param name="provider"></param>
        protected override void Uninitialize(IServiceProvider provider)
        {
            WorkflowQueuingService qService = (WorkflowQueuingService)provider.GetService(typeof(WorkflowQueuingService));
            if (qService != null)
            {
                if (!qService.Exists(QueueName))
                {
                    qService.DeleteWorkflowQueue(QueueName);
                }
            }

            base.Uninitialize(provider);
        }
        #region IEventActivity Members

        /// <summary>
        /// the name of the queue, unique within this workflow
        /// </summary>
        [Browsable(false)]
        public IComparable QueueName
        {
            get
            {
                return SynchronizationService.QUEUE_NAME + QualifiedName;
            }
        }

        /// <summary>
        /// Registers for items to be available.
        /// </summary>
        /// <param name="parentContext"></param>
        /// <param name="parentEventHandler"></param>
        public void Subscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler)
        {
            WorkflowQueue queue = GetWorkflowQueue(parentContext);
            queue.RegisterForQueueItemAvailable(parentEventHandler, QualifiedName);
        }

        /// <summary>
        /// Removes the listener from the queue.  
        /// </summary>
        /// <param name="parentContext"></param>
        /// <param name="parentEventHandler"></param>
        public void Unsubscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler)
        {
            WorkflowQueue queue = GetWorkflowQueue(parentContext);
            queue.UnregisterForQueueItemAvailable(parentEventHandler);
        }

        #endregion

        /// <summary>
        /// Execute this activity. This will tell the synchronization service that it is waiting
        /// and wanting to execute its child.
        /// </summary>
        /// <param name="executionContext"></param>
        /// <returns></returns>
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            Subscribe(executionContext, this);

            SynchronizationService synchronizationService = executionContext.GetService<SynchronizationService>();
            synchronizationService.AddWaitingWorkflow(this.WorkflowInstanceId, QueueName, SharingName);

            return ActivityExecutionStatus.Executing;
        }

        #region IActivityEventListener<QueueEventArgs> Members

        /// <summary>
        /// we received a queue item available event and can proceed to execute our child if the queue item
        /// is really available
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnEvent(object sender, QueueEventArgs e)
        {
            ActivityExecutionContext executionContext = sender as ActivityExecutionContext;
            if (executionContext == null)
                throw new ArgumentException("Sender must be ActivityExecutionContext");

            SynchronizationService synchronizationService = executionContext.GetService<SynchronizationService>();

            WorkflowQueue queue = GetWorkflowQueue(executionContext);
            object data = queue.Dequeue();
            //use the data
            if (data != null)
            {
                Unsubscribe(executionContext, this);

                if (this.EnabledActivities.Count > 0)
                {
                    Activity child = this.EnabledActivities[0];
                    child.Closed += this.ChildClosed;
                    executionContext.ExecuteActivity(child);
                }
                else
                {
                    synchronizationService.StartNextWaitingWorkflowByName(SharingName);
                    executionContext.CloseActivity();
                }
            }
        }

        #endregion

        /// <summary>
        /// our child has finished executing, so tell the synchronization service and closed this activity
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ChildClosed(object sender, ActivityExecutionStatusChangedEventArgs e)
        {
            e.Activity.Closed -= this.ChildClosed;

            ActivityExecutionContext executionContext = sender as ActivityExecutionContext;
            SynchronizationService synchronizationService = executionContext.GetService<SynchronizationService>();
            synchronizationService.StartNextWaitingWorkflowByName(SharingName);

            executionContext.CloseActivity();
        }

        /// <summary>
        /// get the workflow queue for at his synchronization string
        /// </summary>
        /// <param name="executionContext"></param>
        /// <exception cref="System.Exception">thrown when the queue cannot be found</exception>
        /// <returns>the queue</returns>
        private WorkflowQueue GetWorkflowQueue(ActivityExecutionContext executionContext)
        {
            WorkflowQueuingService queuingService = executionContext.GetService<WorkflowQueuingService>();
            WorkflowQueue queue = queuingService.GetWorkflowQueue(QueueName);
            return queue;
        }
    }

    public class RealSynchronizationScopeDesigner : CompositeActivityDesigner
    {
        protected override Rectangle ImageRectangle
        {
            get
            {
                Rectangle rect = base.ImageRectangle;
                rect.Size = Properties.Resources.ActivityImage.Size;
                return rect;
            }
        }

        protected override void Initialize(Activity activity)
        {
            base.Initialize(activity);
            Bitmap image = Properties.Resources.ActivityImage;
            image.MakeTransparent();
            this.Image = image;
        }
    }

    /// <summary>
    /// validator that validates that there is no more than one child activity.
    /// this activity can handle zero children although that's not a very useful state.
    /// </summary>
    public class RealSynchronizationScopeValidator : ActivityValidator
    {
        public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
        {
            if (manager == null)
                throw new ArgumentNullException("manager");
            if (obj == null)
                throw new ArgumentNullException("obj");
            RealSynchronizationScope activity = obj as RealSynchronizationScope;
            if (activity == null)
                throw new InvalidOperationException("obj should be a RealSynchronizationScope");
            if (activity.Parent != null)
            {
                ValidationErrorCollection errors = base.Validate(manager, obj);
                if (String.IsNullOrEmpty(activity.SharingName))
                    errors.Add(ValidationError.GetNotSetValidationError("SharingName"));
                if (activity.Activities.Count > 1)
                {
                    errors.Add(new ValidationError("There can be only one child activity", 3));
                }
                return errors;
            }
            else
            {
                return new ValidationErrorCollection();
            }
        }
    }
}